package com.swak.utils.time;

import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.Period;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;

import org.springframework.util.Assert;

import com.swak.Constants;
import com.swak.utils.Maps;
import com.swak.utils.StringUtils;

/**
 * java8日期工具.
 *
 * @author KimZing - kimzing@163.com
 * @since 2018-08-07 02:02
 */
public final class DateTimes {

	private static Map<String, DateTimeFormatter> Formatters = Maps.newConcurrentMap();

	private static DateTimeFormatter ofPattern(String pattern) {
		Assert.isTrue(StringUtils.isNotBlank(pattern), "Pattern is null");
		if (Formatters.containsKey(pattern)) {
			return Formatters.get(pattern);
		}
		return Formatters.computeIfAbsent(pattern, (key) -> DateTimeFormatter.ofPattern(key));
	}

	private DateTimes() {

	}

	// 获取当前时间的LocalDateTime对象
	// LocalDateTime.now()

	// 根据年月日构建
	// LocalDateTime.of()

	// 比较日期先后
	// LocalDateTime.now().isBefore()
	// LocalDateTime.now().isAfter()

	public static LocalDateTime now() {
		return LocalDateTime.now();
	}

	public static LocalTime nowTime() {
		return LocalTime.now();
	}

	public static Long nowMilli() {
		return LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
	}

	public static Long nowSeconds() {
		return LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
	}

	/**
	 * long 转换为 LocalDateTime
	 */
	public static LocalDateTime getDateTimeOfTimestamp(long timestamp) {
		Instant instant = Instant.ofEpochMilli(timestamp);
		ZoneId zone = ZoneId.systemDefault();
		return LocalDateTime.ofInstant(instant, zone);
	}

	/**
	 * long 转换为 LocalDateTime
	 */
	public static LocalDateTime getDateTimeOfSeconds(long seconds) {
		Instant instant = Instant.ofEpochSecond(seconds);
		ZoneId zone = ZoneId.systemDefault();
		return LocalDateTime.ofInstant(instant, zone);
	}

	/**
	 * Date转换为LocalDateTime.
	 */
	public static LocalDateTime convertDateToLDT(Date date) {
		return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
	}

	/**
	 * 格式化Unix to LocalDateTime
	 */
	public static String formatUnixTimeToLocalDateTime(long unixTimeInMilliSecond, String pattern) {
		LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTimeInMilliSecond),
				ZoneOffset.ofHours(8));
		return time.format(DateTimes.ofPattern(pattern));
	}

	/**
	 * LocalDateTime转换为Date.
	 */
	public static Date convertLDTToDate(LocalDateTime time) {
		return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
	}

	/**
	 * 获取指定日期的毫秒.
	 */
	public static Long getMilliByTime(LocalDateTime time) {
		return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
	}

	/**
	 * 获取指定日期的秒.
	 */
	public static Long getSecondsByTime(LocalDateTime time) {
		return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
	}

	/**
	 * 获取指定时间的指定格式.
	 */
	public static String getFormatTime(LocalDateTime time, String pattern) {
		return time.format(DateTimes.ofPattern(pattern));
	}

	/**
	 * 获取指定时间的指定格式.
	 */
	public static String getFormatTime(LocalTime time, String pattern) {
		return time.format(DateTimes.ofPattern(pattern));
	}

	/**
	 * 获取当前时间的指定格式.
	 */
	public static String getFormatNow(String pattern) {
		return getFormatTime(LocalDateTime.now(), pattern);
	}

	/**
	 * 日期加上一个数,根据field不同加不同值,field为ChronoUnit.*.
	 */
	public static LocalDateTime plus(LocalDateTime time, long number, ChronoUnit field) {
		return time.plus(number, field);
	}

	/**
	 * 多少天之后的日期
	 * 
	 * @param time 当前的时间
	 * @param days offset 的天数
	 * @return
	 */
	public static LocalDateTime getDateByNowOffset(long days) {
		return LocalDateTime.now().plus(days, ChronoUnit.DAYS);
	}

	/**
	 * 多少天之后的日期
	 * 
	 * @param time 当前的时间
	 * @param days offset 的天数
	 * @return
	 */
	public static LocalDateTime getDateByOffset(LocalDateTime time, long days) {
		return time.plus(days, ChronoUnit.DAYS);
	}

	/**
	 * 日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.*.
	 */
	public static LocalDateTime minu(LocalDateTime time, long number, ChronoUnit field) {
		return time.minus(number, field);
	}

	/**
	 * 获取两个日期的差 field参数为ChronoUnit.*. 注意：先转化为 ms，在格式化其他的时间格式，不满则略去
	 */
	public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) {
		Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime));
		if (field == ChronoUnit.YEARS) {
			return period.getYears();
		}
		if (field == ChronoUnit.MONTHS) {
			return period.getYears() * 12L + period.getMonths();
		}
		return field.between(startTime, endTime);
	}

	/**
	 * 获取两个日期的差 field参数为ChronoUnit.*. 注意：先转化为 ms，在格式化其他的时间格式，不满则略去
	 */
	public static long betweenTwoTimeSmart(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) {
		LocalDateTime _start = startTime.isBefore(endTime) ? startTime : endTime;
		LocalDateTime _end = startTime.isBefore(endTime) ? endTime : startTime;
		return betweenTwoTime(_start, _end, field);
	}

	/**
	 * 获取一天的开始时间，2017,7,22 00:00.
	 */
	public static LocalDateTime getNowStart() {
		return LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
	}

	/**
	 * 获取时段的开始时间，2017,7,22 01:00.
	 */
	public static LocalDateTime getHourStart(LocalDateTime time) {
		return time.withMinute(0).withSecond(0).withNano(0);
	}

	/**
	 * 获取一天的开始时间，2017,7,22 00:00.
	 */
	public static LocalDateTime getDayStart(LocalDateTime time) {
		return time.withHour(0).withMinute(0).withSecond(0).withNano(0);
	}

	/**
	 * 获取一天的结束时间，2017,7,22 23:59:59.999999999.
	 */
	public static LocalDateTime getDayEnd(LocalDateTime time) {
		return time.withHour(23).withMinute(59).withSecond(59).withNano(0);
	}

	/**
	 * 获取一天所在周的第一天
	 */
	public static LocalDateTime getWeekStart(LocalDateTime time) {
		return getDayStart(time).with(DayOfWeek.MONDAY);
	}

	/**
	 * 获取一天所在周的第几天
	 */
	public static LocalDateTime getWeekDay(LocalDateTime time, int days) {
		return getDayStart(time).with(DayOfWeek.MONDAY).plusDays(days - 1);
	}

	/**
	 * 获取一天所在周的第一天
	 */
	public static LocalDateTime getWeekEnd(LocalDateTime time) {
		return getDayStart(time).with(DayOfWeek.SUNDAY);
	}

	/**
	 * 获取一天所在月的第一天
	 */
	public static LocalDateTime getMonthStart(LocalDateTime time) {
		return getDayStart(time).with(TemporalAdjusters.firstDayOfMonth());
	}

	/**
	 * 获取一天的结束时间，2017,7,22 23:59:59.999999999.
	 */
	public static LocalDateTime getMonthEnd(LocalDateTime time) {
		return getDayStart(time).with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59)
				.withNano(0);
	}

	/**
	 * 多少月之后的日期
	 * 
	 * @param time 当前的时间
	 * @param days offset 的天数
	 * @return
	 */
	public static LocalDateTime getDateByMonthOffset(LocalDateTime time, long months) {
		return time.plus(months, ChronoUnit.MONTHS);
	}

	/**
	 * 获取一天所在月的第几天
	 */
	public static LocalDateTime getMonthDay(LocalDateTime time, int days) {
		return getDayStart(time).with(TemporalAdjusters.firstDayOfMonth()).plusDays(days - 1);
	}

	/**
	 * 获取一天所在季度的第一天
	 */
	public static LocalDateTime getQuarterStart(LocalDateTime time) {
		int month = time.getMonthValue();
		LocalDate firstDayOfQuarter;
		if (month <= 3) {
			firstDayOfQuarter = LocalDate.of(time.getYear(), Month.JANUARY, 1);
		} else if (month <= 6) {
			firstDayOfQuarter = LocalDate.of(time.getYear(), Month.APRIL, 1);
		} else if (month <= 9) {
			firstDayOfQuarter = LocalDate.of(time.getYear(), Month.JULY, 1);
		} else {
			firstDayOfQuarter = LocalDate.of(time.getYear(), Month.OCTOBER, 1);
		}
		return firstDayOfQuarter.atStartOfDay();
	}

	/**
	 * 获取一天所在年的第一天
	 */
	public static LocalDateTime getYearStart(LocalDateTime time) {
		return getDayStart(time).with(TemporalAdjusters.firstDayOfYear());
	}

	/**
	 * 获取一天所在年的第一天
	 */
	public static LocalDateTime getYearEnd(LocalDateTime time) {
		return getDayStart(time).with(TemporalAdjusters.lastDayOfYear()).withHour(23).withMinute(59).withSecond(59)
				.withNano(0);
	}

	/**
	 * 格式化
	 */
	public static LocalDateTime parseDateTime(String datetime, String formate) {
		return LocalDateTime.parse(datetime, DateTimes.ofPattern(formate));
	}

	/**
	 * 格式化
	 */
	public static LocalTime parseTime(String time, String formate) {
		return LocalTime.parse(time, DateTimes.ofPattern(formate));
	}

	/**
	 * 格式化
	 */
	public static LocalDateTime parseDate(String date, String formate) {
		return LocalDate.parse(date, DateTimes.ofPattern(formate)).atStartOfDay();
	}

	/**
	 * 格式化
	 */
	public static LocalDateTime parseMonth(String month, String formate) {
		YearMonth yearMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern(formate));
		return yearMonth.atDay(1).atStartOfDay();
	}

	/**
	 * 是否过期
	 */
	public static boolean expired(LocalDateTime time, long expiredMillis) {
		LocalDateTime expired = plus(time, expiredMillis, ChronoUnit.MILLIS);
		return expired.isBefore(LocalDateTime.now());
	}

	/**
	 * 返回一个指定日期的过期时间
	 */
	public static LocalDateTime setExpired(LocalDateTime time, long expiredMillis) {
		return plus(time, expiredMillis, ChronoUnit.MILLIS);
	}

	/**
	 * 返回一个指定日期的过期时间
	 */
	public static LocalDateTime setExpiredNow(long expiredMillis) {
		return plus(now(), expiredMillis, ChronoUnit.MILLIS);
	}

	/**
	 * 当前第几周
	 * 
	 * @param time
	 * @return
	 */
	public static int getWeekOfYear(LocalDateTime time) {
		WeekFields weekFields = WeekFields.ISO;
		return time.get(weekFields.weekOfYear());
	}

	/**
	 * 周几
	 * 
	 * @param time
	 * @return
	 */
	public static int getDayOfWeek(LocalDateTime time) {
		WeekFields weekFields = WeekFields.ISO;
		return time.get(weekFields.dayOfWeek());
	}

	/**
	 * 周几
	 * 
	 * @param time
	 * @return
	 */
	public static String getDayNameOfWeek(LocalDateTime time) {
		int day = getDayOfWeek(time);
		return Constants.Week_Days[day - 1];
	}

	/**
	 * 星期几
	 * 
	 * @param time
	 * @return
	 */
	public static String getDayName2OfWeek(LocalDateTime time) {
		int day = getDayOfWeek(time);
		return Constants.Week_Day2s[day - 1];
	}

	/**
	 * 取一个最小的值
	 * 
	 * @param beginDate
	 * @param endDate
	 * @return
	 */
	public static LocalDateTime min(java.time.LocalDateTime beginDate, java.time.LocalDateTime endDate) {
		if (beginDate != null && endDate != null) {
			return beginDate.isBefore(endDate) ? beginDate : endDate;
		}
		return beginDate != null ? beginDate : endDate;
	}

	/**
	 * 计算指定生日在某年的年龄
	 *
	 * @param birthday      生日
	 * @return 年龄
	 */
	public static int ageOfNow(java.time.LocalDateTime birthday) {
		return age(birthday.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
				LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
	}

	/**
	 * 计算相对于dateToCompare的年龄，常用于计算指定生日在某年的年龄
	 *
	 * @param birthday      生日
	 * @param dateToCompare 需要对比的日期
	 * @return 年龄
	 */
	public static int age(java.time.LocalDateTime birthday, java.time.LocalDateTime dateToCompare) {
		return age(birthday.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
				dateToCompare.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
	}

	/**
	 * 计算相对于dateToCompare的年龄，常用于计算指定生日在某年的年龄
	 *
	 * @param birthday      生日
	 * @param dateToCompare 需要对比的日期
	 * @return 年龄
	 */
	public static int age(long birthdayMs, long dateToCompareMs) {
		if (birthdayMs > dateToCompareMs) {
			throw new IllegalArgumentException("Birthday is after dateToCompare!");
		}

		final Calendar cal = Calendar.getInstance();
		cal.setTimeInMillis(dateToCompareMs);

		final int year = cal.get(Calendar.YEAR);
		final int month = cal.get(Calendar.MONTH);
		final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
		final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);

		cal.setTimeInMillis(birthdayMs);
		int age = year - cal.get(Calendar.YEAR);

		final int monthBirth = cal.get(Calendar.MONTH);

		// 当前日期，则为0岁
		if (age == 0) {
			return 0;
		} else if (month == monthBirth) {

			final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);
			final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
			if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth <= dayOfMonthBirth) {
				// 如果生日在当月，但是未超过生日当天的日期，年龄减一
				age--;
			}
		} else if (month < monthBirth) {
			// 如果当前月份未达到生日的月份，年龄计算减一
			age--;
		}

		return age;
	}
}
